import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import subprocess
import json
import os
import sqlite3
import pandas as pd
import threading
import sys
import time

class WeChatExportTool:
    def __init__(self, root):
        self.root = root
        self.root.title("微信联系人导出工具")
        self.root.geometry("800x600")
        self.root.resizable(False, False)
        
        # 初始化变量
        # 存储微信数据库密钥
        self.wechat_key = tk.StringVar()
        # 存储微信数据库路径
        self.wechat_path = tk.StringVar()
        # 存储待解密的微信数据库文件路径
        self.db_path = tk.StringVar()
        # 存储解密后的数据库文件路径
        self.decrypted_path = tk.StringVar()
        # 存储联系人导出文件的路径
        self.export_path = tk.StringVar()
        # 存储当前操作步骤，初始值为1
        self.current_step = tk.IntVar(value=1)
        
        # 创建界面
        self.create_widgets()

    def getExportFileName(self):
        """获取导出文件名"""
        return time.strftime("%Y%m%d%H%M%S", time.localtime())+".xlsx"
        
    def create_widgets(self):
        # 标题
        # title_label = ttk.Label(self.root, text="微信联系人导出工具", font=("Arial", 16, "bold"))
        # title_label.pack(pady=10)
        
        # 步骤指示器 - 先创建，确保显示在顶部
        self.create_step_indicator()
        
        # 内容区域 - 后创建，占据剩余空间
        self.content_frame = ttk.Frame(self.root)
        self.content_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
        
        # 初始化第一步
        self.show_step_1()
        
    def create_step_indicator(self):
        """
        创建步骤指示器
        
        功能：
        - 显示当前进度步骤
        - 高亮当前步骤
        - 提供视觉引导
        
        参数：无
        返回值：无
        """
        # 创建步骤指示器的主框架，用于容纳所有步骤元素
        step_frame = ttk.Frame(self.root)
        # 将步骤框架填充到窗口中，设置水平和垂直边距
        step_frame.pack(fill=tk.X, padx=20, pady=15)
        
        # 步骤配置：定义三个步骤的文本标签
        steps = ["获取密钥", "解密数据库", "导出联系人"]
        
        # 标记步骤指示器frame，便于后续识别和更新
        step_frame._step_indicator = True
        
        # 获取当前步骤值，确保在创建时使用正确的值
        current_step_value = self.current_step.get()
        
        # 创建步骤容器
        # 使用 enumerate 函数遍历 steps 列表，从 1 开始编号
        # i 为当前步骤的编号，step_text 为当前步骤对应的文本
        # enumerate(steps, 1) 会为 steps 列表中的每个元素分配一个从 1 开始的序号
        # 打印 enumerate(steps, 1) 的结果，查看每个元素的序号和文本
        # print(list(enumerate(steps, 1)))
        for i, step_text in enumerate(steps, 1):
            # 调试输出：打印当前步骤编号和文本
            # print(i, step_text)
            
            # 步骤容器：为每个步骤创建一个独立的框架
            step_container = ttk.Frame(step_frame)
            # 使用网格布局，将步骤容器放置在步骤框架的第 i-1 列（从0开始）
            step_container.grid(row=0, column=i-1, padx=5)
            
            # 步骤圆圈：创建用于显示步骤编号的圆圈框架
            circle_frame = ttk.Frame(step_container)
            # 设置圆圈框架的垂直间距，底部留出5像素空白
            circle_frame.pack(pady=(0, 5))
            
            # 判断当前步骤是否为高亮状态：比较步骤编号与当前步骤值
            is_current_step = (i == current_step_value)
            
            # 步骤编号圆圈：创建显示步骤编号的标签
            circle_label = tk.Label(circle_frame, 
                                  # 显示步骤编号
                                  text=str(i),
                                  # 设置字体样式：当前步骤加粗，其他正常
                                  font=("Arial", 12, "bold" if is_current_step else "normal"),
                                  # 设置标签尺寸：宽度3个字符，高度1行
                                  width=3, height=1,
                                  # 设置背景色：当前步骤绿色，其他灰色
                                  bg="#4CAF50" if is_current_step else "#E0E0E0",
                                  # 设置文字颜色：当前步骤白色，其他深灰色
                                  fg="white" if is_current_step else "#666666",
                                  # 设置边框样式：实线边框
                                  relief="solid",
                                  # 设置边框宽度：1像素
                                  bd=1)
            # 将圆圈标签添加到圆圈框架中
            circle_label.pack()
            
            # 步骤文本：创建显示步骤描述的标签
            step_label = ttk.Label(step_container, 
                                  # 显示步骤文本内容
                                  text=step_text,
                                  # 设置字体样式：当前步骤加粗，其他正常
                                  font=("Arial", 10, "bold" if is_current_step else "normal"),
                                  # 设置文字颜色：当前步骤绿色，其他深灰色
                                  foreground="#4CAF50" if is_current_step else "#666666")
            # 将步骤文本标签添加到步骤容器中
            step_label.pack()
            
            # 步骤之间的连接线（除了最后一个步骤）
            if i < len(steps):
                # 创建连接线框架，用于放置步骤间的连接线
                separator_frame = ttk.Frame(step_frame)
                # 使用网格布局，将连接线框架放置在步骤之间，设置填充和拉伸
                # 修正：连接线应该放在当前步骤的右侧，即第i列（从0开始）
                separator_frame.grid(row=0, column=i, sticky="we", padx=2)
                
                # 创建连接线画布：用于绘制步骤间的连线
                separator_canvas = tk.Canvas(separator_frame, 
                                            # 设置画布高度为2像素，背景色为灰色
                                            height=2, bg="#E0E0E0")
                # 将画布填充到连接线框架中，允许水平拉伸
                separator_canvas.pack(fill=tk.X, expand=True)
                
                # 绘制连接线：在画布上创建一条水平线
                separator_canvas.create_line(0, 1, 50, 1, 
                                           # 设置线条颜色：已完成步骤绿色，未完成灰色
                                           fill="#4CAF50" if i < current_step_value else "#E0E0E0",
                                           # 设置线条宽度：2像素
                                           width=2)
        
        # 绑定步骤指示器更新：当current_step变量值改变时，自动更新步骤指示器
        self.current_step.trace_add('write', self.update_step_indicator)
    
    def update_step_indicator(self, *args):
        """
        更新步骤指示器样式
        
        功能：
        - 根据当前步骤更新步骤指示器的视觉状态
        - 高亮当前步骤，标记已完成步骤
        
        参数：
        - *args: 回调参数（忽略）
        
        返回值：无
        """
        # 由于步骤指示器是动态创建的，这里需要重新创建整个指示器
        # 在实际应用中，可以优化为只更新样式而不重新创建
        
        # 找到步骤指示器的frame并重新创建
        for widget in self.root.winfo_children():
            if isinstance(widget, ttk.Frame) and hasattr(widget, '_step_indicator'):
                widget.destroy()
                break
        
        # 重新创建步骤指示器
        self.create_step_indicator()
    
    def clear_content_frame(self):
        for widget in self.content_frame.winfo_children():
            widget.destroy()
    
    def show_step_1(self):
        self.clear_content_frame()
        self.current_step.set(1)
        
        # 步骤1内容
        step1_label = ttk.Label(self.content_frame, text="第一步：获取微信数据库密钥", 
                               font=("Arial", 12, "bold"))
        step1_label.pack(anchor=tk.W, pady=(0, 10))
        
        # 说明文字
        desc_text = """请确保：
1. 微信已在电脑上登录
2. 工具需要获取微信数据库密钥，请点击下方按钮开始"""
        
        desc_label = ttk.Label(self.content_frame, text=desc_text, justify=tk.LEFT)
        desc_label.pack(anchor=tk.W, pady=(0, 15))
        
        # 获取密钥按钮
        key_button = ttk.Button(self.content_frame, text="获取微信数据库密钥", 
                               command=self.get_wechat_key)
        key_button.pack(pady=5)
        
        # 密钥显示
        key_frame = ttk.Frame(self.content_frame)
        key_frame.pack(fill=tk.X, pady=10)
        
        ttk.Label(key_frame, text="获取的密钥:").pack(side=tk.LEFT)
        key_entry = ttk.Entry(key_frame, textvariable=self.wechat_key, width=50)
        key_entry.pack(side=tk.LEFT, padx=(5, 0), fill=tk.X, expand=True)
        
        # 日志区域
        log_label = ttk.Label(self.content_frame, text="操作日志:")
        log_label.pack(anchor=tk.W, pady=(15, 5))
        
        self.log_text_1 = tk.Text(self.content_frame, height=8, width=70)
        self.log_text_1.pack(fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(self.log_text_1, command=self.log_text_1.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text_1.config(yscrollcommand=scrollbar.set)
        
        # 下一步按钮
        next_button = ttk.Button(self.content_frame, text="下一步", 
                                command=self.show_step_2, state=tk.DISABLED)
        next_button.pack(pady=10)
        self.step1_next_button = next_button
        
    def show_step_2(self):
        self.clear_content_frame()
        self.current_step.set(2)
        
        # 步骤2内容
        step2_label = ttk.Label(self.content_frame, text="第二步：解密微信数据库", 
                               font=("Arial", 12, "bold"))
        step2_label.pack(anchor=tk.W, pady=(0, 10))
        
        # 说明文字
        desc_text = """请选择解密方式：

单文件解密：解密用户选择的单个数据库文件（推荐，速度快）
批量解密：解密指定目录下的所有可解密文件（耗时长，适用于完整备份）

通讯录文件：MicroMsg.db  聊天记录文件：ChatMsg.db"""
        
        desc_label = ttk.Label(self.content_frame, text=desc_text, justify=tk.LEFT)
        desc_label.pack(anchor=tk.W, pady=(0, 15))
        
        # 解密方式选择
        decrypt_mode_frame = ttk.Frame(self.content_frame)
        decrypt_mode_frame.pack(fill=tk.X, pady=10)
        
        ttk.Label(decrypt_mode_frame, text="解密方式:").pack(side=tk.LEFT)
        
        # 解密方式选择变量
        self.decrypt_mode = tk.StringVar(value="single")  # 默认选择单文件解密
        
        single_radio = ttk.Radiobutton(decrypt_mode_frame, text="单文件解密（推荐）", 
                                      variable=self.decrypt_mode, value="single")
        single_radio.pack(side=tk.LEFT, padx=(10, 20))
        
        batch_radio = ttk.Radiobutton(decrypt_mode_frame, text="批量解密（耗时）", 
                                     variable=self.decrypt_mode, value="batch")
        batch_radio.pack(side=tk.LEFT)
        
        # 单文件解密区域
        self.single_file_frame = ttk.Frame(self.content_frame)
        self.single_file_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(self.single_file_frame, text="数据库文件:").pack(side=tk.LEFT)
        # 创建输入框，用于显示和编辑微信数据库文件路径，绑定到 self.db_path 变量，宽度设置为 40 个字符
        # 设置默认值 - 只有当wechat_path不为空时才设置默认路径
        if self.wechat_path.get():
            default_db_path = os.path.join(self.wechat_path.get(), "Msg", "MicroMsg.db")
            if os.path.exists(default_db_path):
                self.db_path.set(default_db_path)
            else:
                # 如果默认路径不存在，尝试其他可能的路径
                possible_paths = [
                    os.path.join(self.wechat_path.get(), "Msg", "MicroMsg.db"),
                    os.path.join(self.wechat_path.get(), "Msg", "MSG.db"),
                    os.path.join(self.wechat_path.get(), "Msg", "ChatMsg.db")
                ]
                for path in possible_paths:
                    if os.path.exists(path):
                        self.db_path.set(path)
                        break
        
        db_entry = ttk.Entry(self.single_file_frame, textvariable=self.db_path, width=40)
        db_entry.pack(side=tk.LEFT, padx=(5, 5), fill=tk.X, expand=True)
        
        browse_db_button = ttk.Button(self.single_file_frame, text="浏览", 
                                     command=self.browse_db_file)
        browse_db_button.pack(side=tk.LEFT)
        
        # 批量解密区域
        self.batch_dir_frame = ttk.Frame(self.content_frame)
        self.batch_dir_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(self.batch_dir_frame, text="微信目录:").pack(side=tk.LEFT)
        self.batch_dir_path = tk.StringVar()
        # 设置默认值 - 只有当wechat_path不为空时才设置默认路径
        if self.wechat_path.get():
            default_batch_path = os.path.join(self.wechat_path.get(), "Msg")
            if os.path.exists(default_batch_path):
                self.batch_dir_path.set(default_batch_path)
        
        batch_dir_entry = ttk.Entry(self.batch_dir_frame, textvariable=self.batch_dir_path, width=40)
        batch_dir_entry.pack(side=tk.LEFT, padx=(5, 5), fill=tk.X, expand=True)
        
        browse_batch_button = ttk.Button(self.batch_dir_frame, text="浏览", 
                                        command=self.browse_batch_dir)
        browse_batch_button.pack(side=tk.LEFT)
        
        # 解密按钮
        decrypt_button = ttk.Button(self.content_frame, text="开始解密", 
                                   command=self.decrypt_database)
        decrypt_button.pack(pady=10)
        
        # 进度显示区域
        self.progress_frame = ttk.Frame(self.content_frame)
        self.progress_frame.pack(fill=tk.X, pady=5)
        
        self.progress_label = ttk.Label(self.progress_frame, text="准备解密...")
        self.progress_label.pack(side=tk.LEFT)
        
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(self.progress_frame, variable=self.progress_var, 
                                           maximum=100, length=300)
        self.progress_bar.pack(side=tk.LEFT, padx=(10, 0), fill=tk.X, expand=True)
        
        # 解密路径显示
        decrypted_frame = ttk.Frame(self.content_frame)
        decrypted_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(decrypted_frame, text="解密文件路径:").pack(side=tk.LEFT)
        decrypted_entry = ttk.Entry(decrypted_frame, textvariable=self.decrypted_path, width=40)
        decrypted_entry.pack(side=tk.LEFT, padx=(5, 5), fill=tk.X, expand=True)
        
        # 日志区域
        log_label = ttk.Label(self.content_frame, text="操作日志:")
        log_label.pack(anchor=tk.W, pady=(15, 5))
        
        self.log_text_2 = tk.Text(self.content_frame, height=8, width=70)
        self.log_text_2.pack(fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(self.log_text_2, command=self.log_text_2.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text_2.config(yscrollcommand=scrollbar.set)
        
        # 按钮区域
        button_frame = ttk.Frame(self.content_frame)
        button_frame.pack(pady=10)
        
        ttk.Button(button_frame, text="上一步", 
                  command=self.show_step_1).pack(side=tk.LEFT, padx=(0, 10))
        
        next_button = ttk.Button(button_frame, text="下一步", 
                                command=self.show_step_3, state=tk.DISABLED)
        next_button.pack(side=tk.LEFT)
        self.step2_next_button = next_button
        
        # 初始显示控制
        self.update_decrypt_ui()
        
        # 绑定解密方式变化事件
        self.decrypt_mode.trace('w', self.on_decrypt_mode_changed)
    
    def show_step_3(self):
        self.clear_content_frame()
        self.current_step.set(3)
        
        # 步骤3内容
        step3_label = ttk.Label(self.content_frame, text="第三步：导出联系人到Excel", 
                               font=("Arial", 12, "bold"))
        step3_label.pack(anchor=tk.W, pady=(0, 10))
        
        # 说明文字
        desc_text = """将解密的数据库中的联系人信息导出为Excel文件
        
默认导出自然人联系人，可选择是否导出公众号联系人"""
        
        desc_label = ttk.Label(self.content_frame, text=desc_text, justify=tk.LEFT)
        desc_label.pack(anchor=tk.W, pady=(0, 15))
        
        # 导出选项框架
        options_frame = ttk.Frame(self.content_frame)
        options_frame.pack(fill=tk.X, pady=10)
        
        # 导出公众号联系人选项
        self.export_official_accounts = tk.BooleanVar(value=False)
        official_accounts_check = ttk.Checkbutton(options_frame, 
                                                  text="导出公众号联系人",
                                                  variable=self.export_official_accounts)
        official_accounts_check.pack(anchor=tk.W)
        
        # 导出路径选择
        export_frame = ttk.Frame(self.content_frame)
        export_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(export_frame, text="导出路径:").pack(side=tk.LEFT)
        #判断输出目录是否存在，不存在则创建
        if not os.path.exists("output"):
            os.makedirs("output")
        self.export_path.set(os.path.join("output", self.getExportFileName()))
        export_entry = ttk.Entry(export_frame, textvariable=self.export_path, width=40)
        export_entry.pack(side=tk.LEFT, padx=(5, 5), fill=tk.X, expand=True)
        
        browse_export_button = ttk.Button(export_frame, text="浏览", 
                                         command=self.browse_export_path)
        browse_export_button.pack(side=tk.LEFT)
        
        # 导出按钮
        export_button = ttk.Button(self.content_frame, text="导出联系人到Excel", 
                                  command=self.export_contacts)
        export_button.pack(pady=10)
        
        # 日志区域
        log_label = ttk.Label(self.content_frame, text="操作日志:")
        log_label.pack(anchor=tk.W, pady=(15, 5))
        
        self.log_text_3 = tk.Text(self.content_frame, height=8, width=70)
        self.log_text_3.pack(fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(self.log_text_3, command=self.log_text_3.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text_3.config(yscrollcommand=scrollbar.set)
        
        # 按钮区域
        button_frame = ttk.Frame(self.content_frame)
        button_frame.pack(pady=10)
        
        ttk.Button(button_frame, text="上一步", 
                  command=self.show_step_2).pack(side=tk.LEFT, padx=(0, 10))
        
        ttk.Button(button_frame, text="完成", 
                  command=self.root.quit).pack(side=tk.LEFT)
    
    def log_message(self, log_widget, message):
        """向日志控件添加消息"""
        log_widget.insert(tk.END, message + "\n")
        log_widget.see(tk.END)
        self.root.update_idletasks()
    
    def get_wechat_key(self):
        """获取微信数据库密钥"""
        def run_command():
            try:
                self.log_message(self.log_text_1, "正在获取微信数据库密钥...")
                
                # 使用 pywxdump 的 Python API 获取微信信息
                from pywxdump import get_wx_info
                
                self.log_message(self.log_text_1, "正在扫描微信信息...")
                
                # 获取微信信息（包含密钥）
                wechat_info = get_wx_info()
                
                if wechat_info and len(wechat_info) > 0:
                    self.log_message(self.log_text_1, f"找到 {len(wechat_info)} 个微信账号信息")
                    
                    # 显示找到的微信信息
                    for i, info in enumerate(wechat_info):
                        self.log_message(self.log_text_1, f"微信账号 {i+1}:")
                        self.log_message(self.log_text_1, f"  微信号: {info.get('wxid', '未知')}")
                        self.log_message(self.log_text_1, f"  微信版本: {info.get('version', '未知')}")
                        self.log_message(self.log_text_1, f"  微信目录: {info.get('wx_dir', '未知')}")
                        
                        # 获取密钥和微信目录
                        key = info.get('key', '')
                        wx_dir = info.get('wx_dir', '')
                        
                        # 如果找到密钥，设置密钥
                        if key:
                            self.wechat_key.set(key)
                            self.log_message(self.log_text_1, f"成功获取密钥: {key}")
                        
                        # 如果找到微信目录，设置微信目录
                        if wx_dir:
                            self.wechat_path.set(wx_dir)
                            self.log_message(self.log_text_1, f"成功获取微信目录: {wx_dir}")
                        
                        # 如果同时获取到密钥和微信目录，启用下一步按钮
                        if key and wx_dir:
                            self.step1_next_button.config(state=tk.NORMAL)
                            break
                        elif key:
                            # 只有密钥，也启用下一步按钮
                            self.step1_next_button.config(state=tk.NORMAL)
                            self.log_message(self.log_text_1, "已获取密钥，但未找到微信目录，请手动选择数据库文件")
                        elif wx_dir:
                            # 只有微信目录，也启用下一步按钮
                            self.step1_next_button.config(state=tk.NORMAL)
                            self.log_message(self.log_text_1, "已获取微信目录，但未找到密钥，请手动输入密钥")
                    
                    # 检查是否成功获取到必要信息
                    if not self.wechat_key.get():
                        self.log_message(self.log_text_1, "未能在微信信息中找到密钥")
                        self.log_message(self.log_text_1, "请尝试手动输入密钥或检查微信是否已登录")
                    
                    if not self.wechat_path.get():
                        self.log_message(self.log_text_1, "未能在微信信息中找到微信目录")
                        self.log_message(self.log_text_1, "请手动选择数据库文件")
                else:
                    self.log_message(self.log_text_1, "未找到微信信息")
                    self.log_message(self.log_text_1, "请确保微信已在电脑上登录")
                
            except ImportError:
                self.log_message(self.log_text_1, "pywxdump 模块导入失败")
                self.log_message(self.log_text_1, "请确保已正确安装: pip install pywxdump")
            except Exception as e:
                self.log_message(self.log_text_1, f"获取密钥过程中出错: {str(e)}")
                self.log_message(self.log_text_1, "请检查微信是否已登录并重新尝试")
        
        # 在新线程中运行命令，避免界面冻结
        thread = threading.Thread(target=run_command)
        thread.daemon = True
        thread.start()
    
    def browse_db_file(self):
        """浏览数据库文件"""
        file_path = filedialog.askopenfilename(
            title="选择微信数据库文件",
            filetypes=[("Database files", "*.db"), ("All files", "*.*")],
            initialdir=os.path.join(self.wechat_path.get(), "Msg")
        )
        if file_path:
            self.db_path.set(file_path)
    
    def browse_batch_dir(self):
        """浏览批量解密目录"""
        dir_path = filedialog.askdirectory(title="选择微信目录")
        if dir_path:
            self.batch_dir_path.set(dir_path)
    
    def browse_export_path(self):
        
        """浏览导出路径"""
        file_path = filedialog.asksaveasfilename(
            title="保存Excel文件",
            defaultextension=".xlsx",
            filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
            initialdir=os.path.join("output", self.getExportFileName())
        )
        if file_path:
            self.export_path.set(file_path)
    
    def on_decrypt_mode_changed(self, *args):
        """解密方式变化时的处理"""
        self.update_decrypt_ui()
    
    def update_decrypt_ui(self):
        """根据解密方式更新界面显示"""
        mode = self.decrypt_mode.get()
        
        if mode == "single":
            # 显示单文件解密区域，隐藏批量解密区域
            self.single_file_frame.pack(fill=tk.X, pady=5)
            self.batch_dir_frame.pack_forget()
            self.progress_label.config(text="准备解密单个文件...")
        else:
            # 显示批量解密区域，隐藏单文件解密区域
            self.single_file_frame.pack_forget()
            self.batch_dir_frame.pack(fill=tk.X, pady=5)
            self.progress_label.config(text="准备批量解密...（耗时较长）")
    
    def decrypt_database(self):
        """解密数据库"""
        if not self.wechat_key.get():
            messagebox.showerror("错误", "请先获取微信数据库密钥")
            return
        
        mode = self.decrypt_mode.get()
        
        if mode == "single":
            # 单文件解密验证
            if not self.db_path.get() or not os.path.exists(self.db_path.get()):
                messagebox.showerror("错误", "请选择有效的数据库文件")
                return
            self.decrypt_single_file()
        else:
            # 批量解密验证
            if not self.batch_dir_path.get() or not os.path.exists(self.batch_dir_path.get()):
                messagebox.showerror("错误", "请选择有效的微信目录")
                return
            self.decrypt_batch_files()
    
    def decrypt_single_file(self):
        """单文件解密"""
        def run_decrypt():
            try:
                self.progress_var.set(0)
                self.progress_label.config(text="开始单文件解密...")
                self.log_message(self.log_text_2, "开始单文件解密...")
                
                # 创建 data 目录（如果当前程序目录下不存在）
                data_dir = 'data'
                if not os.path.exists(data_dir):
                    os.makedirs(data_dir)
                
                input_file = self.db_path.get()
                # 使用更简单的输出路径，避免目录结构问题
                output_file = os.path.join(data_dir, 'de_' + os.path.basename(input_file))
                
                self.log_message(self.log_text_2, f"输入文件: {input_file}")
                self.log_message(self.log_text_2, f"期望输出文件: {output_file}")
                self.log_message(self.log_text_2, f"使用密钥: {self.wechat_key.get()}")
                
                # 检查输入文件是否存在
                if not os.path.exists(input_file):
                    self.log_message(self.log_text_2, f"错误: 输入文件不存在: {input_file}")
                    return
                
                # 检查密钥格式
                key = self.wechat_key.get().strip()
                if len(key) != 64:  # 微信密钥通常是64位十六进制
                    self.log_message(self.log_text_2, f"警告: 密钥长度异常 ({len(key)}位)，正常应为64位")
                
                # 更新进度
                self.progress_var.set(30)
                self.progress_label.config(text="正在解密文件...")
                
                # 使用 pywxdump 的 decrypt 函数
                from pywxdump import decrypt
                
                self.log_message(self.log_text_2, "正在调用解密函数...")
                
                # 先清理可能存在的旧文件
                if os.path.exists(output_file):
                    os.remove(output_file)
                
                # 方式1: 直接调用decrypt函数
                try:
                    decrypt(key,input_file, output_file)
                    self.log_message(self.log_text_2, "解密函数调用完成")
                    
                    # 检查期望的输出文件是否生成
                    if os.path.exists(output_file):
                        self.log_message(self.log_text_2, f"✓ 解密文件已生成在期望位置: {output_file}")
                        final_output_file = output_file
                    else:
                        # 如果期望文件不存在，搜索data目录中的所有解密文件
                        self.log_message(self.log_text_2, "搜索data目录中的解密文件...")
                        found_files = []
                        for root, dirs, files in os.walk(data_dir):
                            for file in files:
                                if file.endswith('.db') and file.startswith('de_'):
                                    found_file = os.path.join(root, file)
                                    found_files.append(found_file)
                                    self.log_message(self.log_text_2, f"发现解密文件: {found_file}")
                        
                        if found_files:
                            # 优先选择与输入文件同名的解密文件
                            expected_name = 'de_' + os.path.basename(input_file)
                            for file in found_files:
                                if os.path.basename(file) == expected_name:
                                    final_output_file = file
                                    self.log_message(self.log_text_2, f"✓ 找到匹配的解密文件: {file}")
                                    break
                            else:
                                # 如果没有完全匹配的，选择第一个找到的文件
                                final_output_file = found_files[0]
                                self.log_message(self.log_text_2, f"✓ 使用第一个找到的解密文件: {final_output_file}")
                        else:
                            raise Exception("未找到任何解密文件")
                    
                except Exception as e:
                    self.log_message(self.log_text_2, f"方式1失败: {str(e)}")
                    raise e
                
                # 更新进度
                self.progress_var.set(80)
                self.progress_label.config(text="正在验证解密结果...")
                
                # 验证解密文件
                if os.path.exists(final_output_file):
                    file_size = os.path.getsize(final_output_file)
                    self.log_message(self.log_text_2, f"解密文件大小: {file_size} 字节")
                    
                    # 验证文件是否为有效的SQLite数据库
                    try:
                        conn = sqlite3.connect(final_output_file)
                        cursor = conn.cursor()
                        cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
                        tables = cursor.fetchall()
                        conn.close()
                        
                        self.log_message(self.log_text_2, f"解密文件包含 {len(tables)} 个表")
                        if tables:
                            table_names = [t[0] for t in tables]
                            # 检查是否包含联系人相关的表
                            contact_tables = [t for t in table_names if 'contact' in t.lower() or 'msg' in t.lower()]
                            if contact_tables:
                                self.log_message(self.log_text_2, f"联系人相关表: {', '.join(contact_tables[:5])}")
                            else:
                                self.log_message(self.log_text_2, "前5个表名: " + ", ".join(table_names[:5]))
                        
                    except Exception as e:
                        self.log_message(self.log_text_2, f"警告: 解密文件可能不是有效的SQLite数据库: {e}")
                
                # 最终检查
                if os.path.exists(final_output_file) and os.path.getsize(final_output_file) > 0:
                    self.progress_var.set(100)
                    self.progress_label.config(text="解密完成!")
                    self.decrypted_path.set(final_output_file)
                    self.step2_next_button.config(state=tk.NORMAL)
                    self.log_message(self.log_text_2, f"✓ 单文件解密成功!")
                    self.log_message(self.log_text_2, f"文件路径: {final_output_file}")
                    self.log_message(self.log_text_2, "文件大小: " + 
                                   str(round(os.path.getsize(final_output_file) / 1024 / 1024, 2)) + " MB")
                else:
                    self.progress_label.config(text="解密失败")
                    self.log_message(self.log_text_2, "❌ 错误: 解密文件未生成或文件大小为0")
                    
            except Exception as e:
                self.progress_label.config(text="解密失败")
                self.log_message(self.log_text_2, f"❌ 单文件解密失败: {str(e)}")
                self.log_message(self.log_text_2, "请检查文件路径和密钥是否正确")
        
        # 在新线程中运行
        thread = threading.Thread(target=run_decrypt)
        thread.daemon = True
        thread.start()
    
    def decrypt_batch_files(self):
        """批量解密"""
        def run_batch_decrypt():
            try:
                self.progress_var.set(0)
                self.progress_label.config(text="开始批量解密...（请耐心等待）")
                self.log_message(self.log_text_2, "开始批量解密...")
                self.log_message(self.log_text_2, "注意: 批量解密耗时较长，请耐心等待...")
                
                # 创建 data 目录
                data_dir = os.path.join(os.path.dirname(__file__), 'data')
                if not os.path.exists(data_dir):
                    os.makedirs(data_dir)
                
                wx_dir = self.batch_dir_path.get()
                
                self.log_message(self.log_text_2, f"微信目录: {wx_dir}")
                self.log_message(self.log_text_2, f"输出目录: {data_dir}")
                self.log_message(self.log_text_2, f"使用密钥: {self.wechat_key.get()}")
                
                # 更新进度
                self.progress_var.set(10)
                self.progress_label.config(text="正在扫描可解密文件...")
                
                # 使用 pywxdump 的 batch_decrypt API
                from pywxdump import batch_decrypt
                
                # 执行批量解密
                self.log_message(self.log_text_2, "正在执行批量解密...")
                result = batch_decrypt(self.wechat_key.get(), wx_dir, data_dir)
                
                # 更新进度
                self.progress_var.set(80)
                self.progress_label.config(text="正在处理解密结果...")
                
                if result and len(result) > 0:
                    self.log_message(self.log_text_2, f"处理了 {len(result)} 个文件")
                    
                    # 统计成功和失败的文件
                    success_count = 0
                    for item in result:
                        if isinstance(item, tuple) and len(item) >= 3:
                            success, message, file_path = item[0], item[1], item[2]
                            if success:
                                success_count += 1
                                self.log_message(self.log_text_2, f"✓ 成功解密: {os.path.basename(file_path)}")
                            else:
                                self.log_message(self.log_text_2, f"✗ 解密失败: {message}")
                    
                    # 更新进度
                    self.progress_var.set(100)
                    self.progress_label.config(text=f"批量解密完成! 成功: {success_count}/{len(result)}")
                    
                    # 查找 MicroMsg 数据库文件
                    micro_msg_files = []
                    try:
                        # 检查 data 目录中的文件
                        for entry in os.listdir(data_dir):
                            if entry.endswith('.db') and 'MicroMsg' in entry:
                                micro_msg_files.append(os.path.join(data_dir, entry))
                        
                        # 检查 Msg 子目录
                        msg_dir = os.path.join(data_dir, 'Msg')
                        if os.path.exists(msg_dir):
                            for entry in os.listdir(msg_dir):
                                if entry.endswith('.db') and 'MicroMsg' in entry:
                                    micro_msg_files.append(os.path.join(msg_dir, entry))
                    except Exception as e:
                        self.log_message(self.log_text_2, f"文件检查错误: {e}")
                    
                    if micro_msg_files:
                        decrypted_file = micro_msg_files[0]
                        self.decrypted_path.set(decrypted_file)
                        self.step2_next_button.config(state=tk.NORMAL)
                        self.log_message(self.log_text_2, f"主要数据库文件: {decrypted_file}")
                    elif success_count > 0:
                        self.log_message(self.log_text_2, "警告: 未找到 MicroMsg 数据库，但其他文件解密成功")
                    else:
                        self.log_message(self.log_text_2, "错误: 所有文件解密失败")
                        
                else:
                    self.progress_label.config(text="批量解密失败")
                    self.log_message(self.log_text_2, "错误: 批量解密返回空结果")
                    
            except Exception as e:
                self.progress_label.config(text="批量解密失败")
                self.log_message(self.log_text_2, f"批量解密过程中出错: {str(e)}")
                self.log_message(self.log_text_2, "请检查微信目录和密钥是否正确")
        
        # 在新线程中运行
        thread = threading.Thread(target=run_batch_decrypt)
        thread.daemon = True
        thread.start()

    def export_contacts(self):
        """导出联系人到Excel"""
        if not self.decrypted_path.get() or not os.path.exists(self.decrypted_path.get()):
            messagebox.showerror("错误", "请先成功解密数据库")
            return
        
        if not self.export_path.get():
            messagebox.showerror("错误", "请选择导出路径")
            return
        
        def run_export():
            try:
                self.log_message(self.log_text_3, "开始导出联系人...")
                
                # 连接解密后的数据库
                conn = sqlite3.connect(self.decrypted_path.get())
                
                # 获取所有表名
                cursor = conn.cursor()
                cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
                tables = cursor.fetchall()
                
                self.log_message(self.log_text_3, "数据库中的表:")
                for table in tables:
                    self.log_message(self.log_text_3, f"  - {table[0]}")
                
                # 检查是否存在Contact表和ContactLabel表
                contact_table_exists = any('Contact' in table[0] for table in tables)
                contact_label_table_exists = any('ContactLabel' in table[0] for table in tables)
                
                if not contact_table_exists:
                    self.log_message(self.log_text_3, "错误: 未找到Contact表")
                    return
                
                # 获取标签映射（如果存在ContactLabel表）
                label_mapping = {}
                if contact_label_table_exists:
                    try:
                        cursor.execute("SELECT LabelId, LabelName FROM ContactLabel")
                        label_rows = cursor.fetchall()
                        for label_id, label_name in label_rows:
                            label_mapping[str(label_id)] = label_name if label_name else f"标签{label_id}"
                        self.log_message(self.log_text_3, f"加载了 {len(label_mapping)} 个标签")
                    except Exception as e:
                        self.log_message(self.log_text_3, f"加载标签信息时出错: {e}")
                
                # 构建查询条件
                where_conditions = ["Type = 3"]  # 只查询Type=3的联系人
                
                if not self.export_official_accounts.get():
                    # 默认只导出自然人联系人（VerifyFlag=0）
                    where_conditions.append("VerifyFlag = 0")
                    self.log_message(self.log_text_3, "导出模式: 仅自然人联系人")
                else:
                    # 导出所有联系人（包括公众号）
                    self.log_message(self.log_text_3, "导出模式: 自然人联系人 + 公众号联系人")
                
                where_clause = " AND ".join(where_conditions)
                
                # 读取联系人数据
                query = f"""
                    SELECT 
                        UserName, Alias, NickName, Remark, Type, VerifyFlag, 
                        LabelIDList, DomainList, PYInitial, QuanPin, 
                        RemarkPYInitial, RemarkQuanPin, BigHeadImgUrl, SmallHeadImgUrl
                    FROM Contact 
                    WHERE {where_clause}
                    ORDER BY NickName
                """
                
                df = pd.read_sql_query(query, conn)
                
                # 处理标签信息
                if 'LabelIDList' in df.columns and label_mapping:
                    def get_label_names(label_id_list):
                        """将标签ID列表转换为标签名称列表"""
                        if not label_id_list:
                            return ""
                        
                        label_ids = str(label_id_list).split(",")
                        label_names = []
                        for label_id in label_ids:
                            label_id = label_id.strip()
                            if label_id in label_mapping:
                                label_names.append(label_mapping[label_id])
                        
                        return ", ".join(label_names)
                    
                    df['标签列表'] = df['LabelIDList'].apply(get_label_names)
                    # 移除原始的LabelIDList列
                    df = df.drop('LabelIDList', axis=1)
                
                # 添加联系人类型标识
                def get_contact_type(row):
                    """根据Type和VerifyFlag判断联系人类型"""
                    if row['Type'] == 3:
                        if row['VerifyFlag'] == 0:
                            return "自然人"
                        else:
                            return "公众号"
                    else:
                        return "其他"
                
                df['联系人类型'] = df.apply(get_contact_type, axis=1)
                
                # 关闭连接
                conn.close()
                
                # 统计导出结果
                natural_person_count = len(df[df['联系人类型'] == '自然人'])
                official_account_count = len(df[df['联系人类型'] == '公众号'])
                
                self.log_message(self.log_text_3, f"找到 {len(df)} 条记录")
                self.log_message(self.log_text_3, f"- 自然人联系人: {natural_person_count} 个")
                self.log_message(self.log_text_3, f"- 公众号联系人: {official_account_count} 个")
                
                # 导出到Excel
                df.to_excel(self.export_path.get(), index=False, engine='openpyxl')
                
                self.log_message(self.log_text_3, f"成功导出到: {self.export_path.get()}")
                self.log_message(self.log_text_3, "导出完成!")
                
            except Exception as e:
                self.log_message(self.log_text_3, f"导出过程中出错: {str(e)}")
        
        # 在新线程中运行导出
        thread = threading.Thread(target=run_export)
        thread.daemon = True
        thread.start()

def main():
    # 检查依赖
    try:
        import pywxdump
    except ImportError:
        print("未找到 pywxdump，请先安装: pip install pywxdump")
        return
    
    try:
        import pandas
    except ImportError:
        print("未找到 pandas，请先安装: pip install pandas openpyxl")
        return
    
   
    # 创建主窗口
    root = tk.Tk()
    app = WeChatExportTool(root)
    root.mainloop()

if __name__ == "__main__":
    main()